/* Copyright (c) 2011 Danish Maritime Authority. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.maritimecloud.identityregistry.resource; import java.util.UUID; import javax.ws.rs.Consumes; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.HeaderParam; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import net.maritimecloud.common.resource.AbstractCommandResource; import net.maritimecloud.common.resource.JsonCommandHelper; import static net.maritimecloud.common.resource.JsonCommandHelper.identityIsEmpty; import static net.maritimecloud.common.resource.RestCommandUtil.resolveCommandName; import net.maritimecloud.identityregistry.command.api.ChangeUserEmailAddress; import net.maritimecloud.identityregistry.command.api.ChangeUserPassword; import net.maritimecloud.identityregistry.command.api.RegisterUser; import net.maritimecloud.identityregistry.command.api.VerifyEmailAddress; import net.maritimecloud.identityregistry.query.UserEntry; import net.maritimecloud.identityregistry.query.UserQueryRepository; import net.maritimecloud.portal.application.ApplicationServiceRegistry; import net.maritimecloud.serviceregistry.query.OrganizationMembershipEntry; import org.axonframework.commandhandling.gateway.CommandGateway; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; /** * @author Christoffer Børrild */ @Path("/api/users") public class UserResource extends AbstractCommandResource { private static final Logger LOG = LoggerFactory.getLogger(UserResource.class); @Override protected CommandGateway commandGateway() { return ApplicationServiceRegistry.commandGateway(); } private UserQueryRepository userQueryRepository() { return ApplicationServiceRegistry.userQueryRepository(); } private String overwriteIdentity(String commandJSON, String propertyName, String value) { return JsonCommandHelper.overwriteIdentity(commandJSON, propertyName, value); } private String resolveUserIdOrFail(String username) { UserEntry userEntry = findByUsername(username); assertNotNull(userEntry, "No user found with username " + username); return userEntry.getUserId(); } private UserEntry findByUsername(String aUsername) { return userQueryRepository().findByUsername(aUsername); } private static void assertNotNull(Object objectToTestForNull, String message) throws WebApplicationException { if (objectToTestForNull == null) { LOG.warn("Objct not found. {}", message); throw new WebApplicationException(message, Response.Status.NOT_FOUND); } } private void assertSameUser(UserEntry userEntry, String username) { if (!userEntry.getUsername().equals(username)) { LOG.warn("User identity mismatch. {} != {}", userEntry.getUsername(), username); throw new WebApplicationException("User identity mismatch", 404); } } // ------------------------------------------------------- // ------------------------------------------------------- // Commands // ------------------------------------------------------- // ------------------------------------------------------- @POST @Consumes(APPLICATION_JSON_CQRS_COMMAND) @Produces(MediaType.APPLICATION_JSON) //@Path("register") public void registerUserPostCommand(@HeaderParam("Content-type") String contentType, @QueryParam("command") @DefaultValue("") String queryCommandName, String commandJSON) { LOG.info("User POST command"); if (identityIsEmpty(commandJSON, contentType)) { LOG.info("Empty userId -> AUTO creating UUID. Got:"); LOG.info("JSON: " + commandJSON); commandJSON = overwriteIdentity(commandJSON, "userId", UUID.randomUUID().toString()); } sendAndWait(contentType, queryCommandName, commandJSON, RegisterUser.class ); } @PUT @Consumes(APPLICATION_JSON_CQRS_COMMAND) @Produces(MediaType.APPLICATION_JSON) @Path("{username}") public void userPutCommand( @HeaderParam("Content-type") String contentType, @QueryParam("command") @DefaultValue("") String queryCommandName, @PathParam("username") String username, String commandJSON ) { LOG.info("Organization PUT command"); // only allow anonymous access to VerifyEmailAddress and ChangeUserPassword assertUserRole("USER", resolveCommandName(contentType, queryCommandName), ChangeUserEmailAddress.class ); String userId = resolveUserIdOrFail(username); commandJSON = overwriteIdentity(commandJSON, "userId", userId); sendAndWait(contentType, queryCommandName, commandJSON, ChangeUserEmailAddress.class, ChangeUserPassword.class, VerifyEmailAddress.class ); } // ------------------------------------------------------- // ------------------------------------------------------- // Queries // ------------------------------------------------------- // ------------------------------------------------------- @GET @Produces(MediaType.APPLICATION_JSON) public Page<UserEntry> getUsers( @QueryParam("usernamePattern") String usernamePattern, @QueryParam("page") @DefaultValue("0") int page, @QueryParam("size") @DefaultValue("10") int size ) { // shori.ini allows "anon"-access to open for POST - restrict GET programaticly to USER role // (needed by invite-user and admin list users) requiresRoles("USER"); // FIXME: we should hide the email address - it should only be visible from users profile! Pageable pageable = new PageRequest(page, size, new Sort(Sort.Direction.DESC, "username")); return usernamePattern == null ? userQueryRepository().findAll(pageable) : userQueryRepository().findByUsernameContainingIgnoreCase(usernamePattern, pageable); } @GET @Path("count") @Produces(MediaType.APPLICATION_JSON) public String getUsersCount( ) { return "{\"usersCount\":" + userQueryRepository().count() + "}"; } @GET @Path("{username}") @Produces(MediaType.APPLICATION_JSON) public UserEntry getUser(@PathParam("username") String aUsername) { requiresRoles("USER"); LOG.debug("Called getUser with username " + aUsername); LOG.warn("TODO: We should probably not expose this service publicly. Only logged in users should be able to get user info!!! "); // FIXME: user should only be able to access own profile unless is an admin UserEntry userEntry = findByUsername(aUsername); assertNotNull(userEntry, "User not found"); return userEntry; } /** * @param aUsername * @return A list of memberships of the organizations that the user is a member of */ @GET @Path("{username}/orgs") @Consumes(MediaType.APPLICATION_JSON) public Iterable<OrganizationMembershipEntry> queryOrganizationMemberships(@PathParam("username") String aUsername) { requiresRoles("USER"); return ApplicationServiceRegistry.organizationMembershipQueryRepository().findByUsername(aUsername); } @GET @Path("{username}/exist") @Produces(MediaType.APPLICATION_JSON) public String getUsernameExist(@PathParam("username") String username) { return "{\"usernameExist\":" + (usernameExist(username) ? "true" : "false") + "}"; } private boolean usernameExist(String username) { return findByUsername(username) != null; } }